<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
<meta name="viewport" content="width=device-width">
<meta name="theme-color" content="#222">
<meta name="generator" content="Hexo 5.4.0">


  <link rel="apple-touch-icon" sizes="180x180" href="/images/favicon.ico">
  <link rel="icon" type="image/png" sizes="32x32" href="/images/favicon.ico">
  <link rel="icon" type="image/png" sizes="16x16" href="/images/favicon.ico">
  <link rel="mask-icon" href="/images/favicon.ico" color="#222">

<link rel="stylesheet" href="/css/main.css">



<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@5.15.4/css/all.min.css" integrity="sha256-mUZM63G8m73Mcidfrv5E+Y61y7a12O5mW4ezU3bxqW4=" crossorigin="anonymous">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/animate.css@3.1.1/animate.min.css" integrity="sha256-PR7ttpcvz8qrF57fur/yAx1qXMFJeJFiA6pSzWi0OIE=" crossorigin="anonymous">

<script class="next-config" data-name="main" type="application/json">{"hostname":"example.com","root":"/","images":"/images","scheme":"Muse","darkmode":false,"version":"8.8.0","exturl":false,"sidebar":{"position":"left","display":"remove","padding":18,"offset":12},"copycode":true,"bookmark":{"enable":false,"color":"#222","save":"auto"},"mediumzoom":false,"lazyload":false,"pangu":false,"comments":{"style":"tabs","active":null,"storage":true,"lazyload":false,"nav":null},"stickytabs":false,"motion":{"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"fadeInDown","post_body":"fadeInDown","coll_header":"fadeInLeft","sidebar":"fadeInUp"}},"prism":false,"i18n":{"placeholder":"搜索...","empty":"没有找到任何搜索结果：${query}","hits_time":"找到 ${hits} 个搜索结果（用时 ${time} 毫秒）","hits":"找到 ${hits} 个搜索结果"},"path":"/search.xml","localsearch":{"enable":"false;","trigger":"auto","top_n_per_article":1,"unescape":false,"preload":false}}</script><script src="/js/config.js"></script>
<meta name="description" content="volatile 是 Java 提供的一种轻量级的同步机制。Java 语言包含两种内在的同步机制：同步块（或方法）和 volatile 变量，相比于 synchronized（synchronized通常称为重量级锁），volatile更轻量级，因为它不会引起线程上下文的切换和调度。但是volatile 变量的同步性较差（有时它更简单并且开销更低），而且其使用也更容易出错。 一.内存模型的相关概念">
<meta property="og:type" content="article">
<meta property="og:title" content="java 并发编程：volatile 关键字">
<meta property="og:url" content="http://example.com/2022/02/16/java%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%EF%BC%9Avolatile%E5%85%B3%E9%94%AE%E5%AD%97/index.html">
<meta property="og:site_name" content="ADGai&#39;s Blog">
<meta property="og:description" content="volatile 是 Java 提供的一种轻量级的同步机制。Java 语言包含两种内在的同步机制：同步块（或方法）和 volatile 变量，相比于 synchronized（synchronized通常称为重量级锁），volatile更轻量级，因为它不会引起线程上下文的切换和调度。但是volatile 变量的同步性较差（有时它更简单并且开销更低），而且其使用也更容易出错。 一.内存模型的相关概念">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="https://gitee.com/AD-Gai-Code/pic-go/raw/master/212219343783699.jpg">
<meta property="article:published_time" content="2022-02-16T11:08:01.531Z">
<meta property="article:modified_time" content="2022-03-01T13:14:36.228Z">
<meta property="article:author" content="AiLaodu">
<meta property="article:tag" content="java 并发编程">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="https://gitee.com/AD-Gai-Code/pic-go/raw/master/212219343783699.jpg">


<link rel="canonical" href="http://example.com/2022/02/16/java%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%EF%BC%9Avolatile%E5%85%B3%E9%94%AE%E5%AD%97/">



<script class="next-config" data-name="page" type="application/json">{"sidebar":"","isHome":false,"isPost":true,"lang":"zh-CN","comments":true,"permalink":"http://example.com/2022/02/16/java%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%EF%BC%9Avolatile%E5%85%B3%E9%94%AE%E5%AD%97/","path":"2022/02/16/java并发编程：volatile关键字/","title":"java 并发编程：volatile 关键字"}</script>

<script class="next-config" data-name="calendar" type="application/json">""</script>
<title>java 并发编程：volatile 关键字 | ADGai's Blog</title>
  




  <noscript>
    <link rel="stylesheet" href="/css/noscript.css">
  </noscript>
</head>

<body itemscope itemtype="http://schema.org/WebPage" class="use-motion">
  <div class="headband"></div>

  <main class="main">
    <header class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-container">
  <div class="site-nav-toggle">
    <div class="toggle" aria-label="切换导航栏" role="button">
        <span class="toggle-line"></span>
        <span class="toggle-line"></span>
        <span class="toggle-line"></span>
    </div>
  </div>

  <div class="site-meta">

    <a href="/" class="brand" rel="start">
      <i class="logo-line"></i>
      <h1 class="site-title">ADGai's Blog</h1>
      <i class="logo-line"></i>
    </a>
  </div>

  <div class="site-nav-right">
    <div class="toggle popup-trigger">
        <i class="fa fa-search fa-fw fa-lg"></i>
    </div>
  </div>
</div>



<nav class="site-nav">
  <ul class="main-menu menu">
        <li class="menu-item menu-item-home"><a href="/" rel="section"><i class="fa fa-home fa-fw"></i>首页</a></li>
        <li class="menu-item menu-item-tags"><a href="/tags/" rel="section"><i class="fa fa-tags fa-fw"></i>标签</a></li>
        <li class="menu-item menu-item-categories"><a href="/categories/" rel="section"><i class="fa fa-th fa-fw"></i>分类</a></li>
        <li class="menu-item menu-item-archives"><a href="/archives/" rel="section"><i class="fa fa-archive fa-fw"></i>归档</a></li>
      <li class="menu-item menu-item-search">
        <a role="button" class="popup-trigger"><i class="fa fa-search fa-fw"></i>搜索
        </a>
      </li>
  </ul>
</nav>



  <div class="search-pop-overlay">
    <div class="popup search-popup"><div class="search-header">
  <span class="search-icon">
    <i class="fa fa-search"></i>
  </span>
  <div class="search-input-container"></div>
  <span class="popup-btn-close" role="button">
    <i class="fa fa-times-circle"></i>
  </span>
</div>
<div class="search-result-container">
  <div class="algolia-stats"><hr></div>
  <div class="algolia-hits"></div>
  <div class="algolia-pagination"></div>
</div>

    </div>
  </div>

</div>
    </header>

    
  <div class="back-to-top" role="button" aria-label="返回顶部">
    <i class="fa fa-arrow-up"></i>
    <span>0%</span>
  </div>

<noscript>
  <div class="noscript-warning">Theme NexT works best with JavaScript enabled</div>
</noscript>


    <div class="main-inner post posts-expand">


  


<div class="post-block">
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-content" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="http://example.com/2022/02/16/java%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%EF%BC%9Avolatile%E5%85%B3%E9%94%AE%E5%AD%97/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/avatar.gif">
      <meta itemprop="name" content="AiLaodu">
      <meta itemprop="description" content="">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="ADGai's Blog">
    </span>
      <header class="post-header">
        <h1 class="post-title" itemprop="name headline">
          java 并发编程：volatile 关键字
        </h1>

        <div class="post-meta-container">
          <div class="post-meta">
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar"></i>
      </span>
      <span class="post-meta-item-text">发表于</span>

      <time title="创建时间：2022-02-16 19:08:01" itemprop="dateCreated datePublished" datetime="2022-02-16T19:08:01+08:00">2022-02-16</time>
    </span>
      <span class="post-meta-item">
        <span class="post-meta-item-icon">
          <i class="far fa-calendar-check"></i>
        </span>
        <span class="post-meta-item-text">更新于</span>
        <time title="修改时间：2022-03-01 21:14:36" itemprop="dateModified" datetime="2022-03-01T21:14:36+08:00">2022-03-01</time>
      </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-folder"></i>
      </span>
      <span class="post-meta-item-text">分类于</span>
        <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
          <a href="/categories/java-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/" itemprop="url" rel="index"><span itemprop="name">java 并发编程</span></a>
        </span>
    </span>

  
</div>

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">
        <p>volatile 是 Java 提供的一种轻量级的同步机制。Java 语言包含两种内在的同步机制：同步块（或方法）和 volatile 变量，相比于 synchronized（synchronized通常称为重量级锁），volatile更轻量级，因为它不会引起线程上下文的切换和调度。但是volatile 变量的同步性较差（有时它更简单并且开销更低），而且其使用也更容易出错。</p>
<h2 id="一-内存模型的相关概念"><a href="#一-内存模型的相关概念" class="headerlink" title="一.内存模型的相关概念"></a>一.内存模型的相关概念</h2><p>计算机在执行程序时，每条指令都是在 CPU 中执行的，而执行指令过程中，势必涉及到数据的读取和写入。由于程序运行过程中的临时数据是存放在主内存（物理内存）当中的，这时就存在一个问题，由于 CPU 执行速度很快，而从内存读取数据和向内存写入数据的过程跟 CPU 执行指令的速度比起来要慢的多，因此如果任何时候对数据的操作都要通过和内存的交互来进行，会大大降低指令执行的速度。因此在 CPU 里面就有了高速缓存。</p>
<span id="more"></span>

<p>　也就是，当程序在运行过程中，会将运算需要的数据从主存复制一份到CPU的高速缓存当中，那么CPU进行计算时就可以直接从它的高速缓存读取数据和向其中写入数据，当运算结束之后，再将高速缓存中的数据刷新到主存当中。举个简单的例子，比如下面的这段代码：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">i = i + <span class="number">1</span>;</span><br></pre></td></tr></table></figure>

<p>当线程执行这个语句时，会先从主存当中读取<code>i</code>的值，然后复制一份到高速缓存当中，然后 CPU 执行指令对<code>i</code>进行加 1 操作，然后将数据写入高速缓存，最后将高速缓存中i最新的值刷新到主存当中。</p>
<p>这个代码在单线程中运行是没有任何问题的，但是在多线程中运行就会有问题了。在多核 CPU 中，每条线程可能运行于不同的 CPU 中，因此每个线程运行时有自己的高速缓存（对单核 CPU 来说，其实也会出现这种问题，只不过是以线程调度的形式来分别执行的）。本文以多核 CPU 为例。</p>
<p>比如同时有 2 个线程执行这段代码，假如初始时<code>i</code>的值为 0，那么我们希望两个线程执行完之后<code>i</code>的值变为 2。但是事实会是这样吗？</p>
<p>可能存在下面一种情况：初始时，两个线程分别读取<code>i</code>的值存入各自所在的 CPU 的高速缓存当中，然后线程 1 进行加 1 操作，然后把<code>i</code>的最新值 1 写入到内存。此时线程 2 的高速缓存当中<code>i</code>的值还是 0，进行加 1 操作之后，<code>i</code>的值为 1，然后线程 2 把<code>i</code>的值写入内存。</p>
<p>最终结果<code>i</code>的值是 1，而不是 2。这就是著名的缓存一致性问题。通常称这种被多个线程访问的变量为共享变量。</p>
<p>也就是说，如果一个变量在多个 CPU 中都存在缓存（一般在多线程编程时才会出现），那么就可能存在缓存不一致的问题。</p>
<p>为了解决缓存不一致性问题，通常来说有以下2种解决方法：</p>
<p>　　1. 通过在总线加LOCK锁的方式</p>
<p>　　2. 通过缓存一致性协议</p>
<p>　　这 2 种方式都是硬件层面上提供的方式。</p>
<p>在早期的 CPU 当中，是通过在总线上加 LOCK 锁的形式来解决缓存不一致的问题。因为 CPU 和其他部件进行通信都是通过总线来进行的，如果对总线加 LOCK 锁的话，也就是说阻塞了其他 CPU 对其他部件访问（如内存），从而使得只能有一个 CPU 能使用这个变量的内存。比如上面例子中 如果一个线程在执行 <code>i = i +1</code>，如果在执行这段代码的过程中，在总线上发出了 LCOK 锁的信号，那么只有等待这段代码完全执行完毕之后，其他 CPU 才能从变量<code>i</code>所在的内存读取变量，然后进行相应的操作。这样就解决了缓存不一致的问题。</p>
<p>但是上面的方式会有一个问题，由于在锁住总线期间，其他 CPU 无法访问内存，导致效率低下。</p>
<p>所以就出现了缓存一致性协议。最出名的就是 Intel 的<code>MESI</code>协议，<code>MESI</code>协议保证了每个缓存中使用的共享变量的副本是一致的。它核心的思想是：当 CPU 写数据时，如果发现操作的变量是共享变量，即在其他 CPU 中也存在该变量的副本，会发出信号通知其他 CPU 将该变量的缓存行置为无效状态，因此当其他 CPU 需要读取这个变量时，发现自己缓存中缓存该变量的缓存行是无效的，那么它就会从内存重新读取。</p>
<p><img src="https://gitee.com/AD-Gai-Code/pic-go/raw/master/212219343783699.jpg" alt="img"></p>
<h2 id="二-并发编程中的三个概念"><a href="#二-并发编程中的三个概念" class="headerlink" title="二.并发编程中的三个概念"></a>二.并发编程中的三个概念</h2><p>在并发编程中，我们通常会遇到以下三个问题：原子性问题，可见性问题，有序性问题。我们先看具体看一下这三个概念：</p>
<h3 id="2-1-原子性"><a href="#2-1-原子性" class="headerlink" title="2.1 原子性"></a>2.1 原子性</h3><p>原子性：即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断，要么就都不执行。</p>
<p>一个很经典的例子就是银行账户转账问题：</p>
<p>​        比如从账户 A 向账户 B 转 1000 元，那么必然包括 2 个操作：从账户 A 减去 1000 元，往账户 B 加上 1000 元。</p>
<p>​        这 2 个操作必须要具备原子性才能保证不出现一些意外的问题。</p>
<p>　　同样地反映到并发编程中会出现什么结果呢？</p>
<p>　　举个最简单的例子，大家想一下假如为一个 32 位的变量赋值过程不具备原子性的话，会发生什么后果？</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">i = 9;</span><br></pre></td></tr></table></figure>

<p> 　假若一个线程执行到这个语句时，我暂且假设为一个32位的变量赋值包括两个过程：为低 16 位赋值，为高 16 位赋值。</p>
<p>　　那么就可能发生一种情况：当将低 16 位数值写入之后，突然被中断，而此时又有一个线程去读取i的值，那么读取到的就是错误的数据。</p>
<h3 id="2-2-可见性"><a href="#2-2-可见性" class="headerlink" title="2.2 可见性"></a>2.2 可见性</h3><p>​        可见性是指当多个线程访问同一个变量时，一个线程修改了这个变量的值，其他线程能够立即看得到修改的值。</p>
<p>　　 举个简单的例子，看下面这段代码：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//线程1执行的代码</span></span><br><span class="line"><span class="keyword">int</span> i = <span class="number">0</span>;</span><br><span class="line">i = <span class="number">10</span>;</span><br><span class="line"> </span><br><span class="line"><span class="comment">//线程2执行的代码</span></span><br><span class="line">j = i;</span><br></pre></td></tr></table></figure>

<p>​        假若执行线程 1 的是 CPU1，执行线程 2 的是 CPU2。由上面的分析可知，当线程 1 执行 <code>i =10</code>这句时，会先把<code>i</code>的初始值加载到 CPU1 的高速缓存中，然后赋值为 10，那么在 CPU1 的高速缓存当中<code>i</code>的值变为 10 了，却没有立即写入到主存当中。</p>
<p>　　此时线程2执行 <code>j = i</code>，它会先去主存读取<code>i</code>的值并加载到 CPU2 的缓存当中，注意此时内存当中<code>i</code>的值还是0，那么就会使得<code>j</code>的值为0，而不是10.</p>
<p>　　这就是可见性问题，线程 1 对变量<code>i</code>修改了之后，线程 2 没有立即看到线程 1 修改的值。</p>
<h3 id="2-3-有序性"><a href="#2-3-有序性" class="headerlink" title="2.3 有序性"></a>2.3 有序性</h3><p>有序性：即程序执行的顺序按照代码的先后顺序执行。举个简单的例子，看下面这段代码</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span> i = <span class="number">0</span>;              </span><br><span class="line"><span class="keyword">boolean</span> flag = <span class="keyword">false</span>;</span><br><span class="line">i = <span class="number">1</span>;                <span class="comment">//语句1  </span></span><br><span class="line">flag = <span class="keyword">true</span>;          <span class="comment">//语句2</span></span><br></pre></td></tr></table></figure>

<p>上面代码定义了一个int型变量，定义了一个 <code>boolean </code> 类型变量，然后分别对两个变量进行赋值操作。从代码顺序上看，语句1是在语句2前面的，那么 JVM 在真正执行这段代码的时候会保证语句1一定会在语句2前面执行吗？不一定，为什么呢？这里可能会发生<strong>指令重排序（Instruction Reorder）</strong>。</p>
<p>　　下面解释一下什么是指令重排序，一般来说，处理器为了提高程序运行效率，可能会对输入代码进行优化，它不保证程序中各个语句的执行先后顺序同代码中的顺序一致，但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。</p>
<p>　　比如上面的代码中，语句1和语句2谁先执行对最终的程序结果并没有影响，那么就有可能在执行过程中，语句2先执行而语句1后执行。</p>
<p>　　但是要注意，虽然处理器会对指令进行重排序，但是它会保证程序最终结果会和代码顺序执行结果相同，那么它靠什么保证的呢？再看下面一个例子：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span> a = <span class="number">10</span>; <span class="comment">//语句1</span></span><br><span class="line"><span class="keyword">int</span> r = <span class="number">2</span>;  <span class="comment">//语句2</span></span><br><span class="line">a = a + <span class="number">3</span>;  <span class="comment">//语句3</span></span><br><span class="line">r = a * a;    <span class="comment">//语句4</span></span><br></pre></td></tr></table></figure>

<p>这段代码有4个语句，那么可能的一个执行顺序是：2 -&gt; 1 -&gt; 3 -&gt; 4</p>
<p>​        那么可不可能是这个执行顺序呢： 语句2 -&gt; 语句1 -&gt; 语句4 -&gt; 语句3</p>
<p>　　不可能，因为处理器在进行重排序时是会考虑指令之间的数据依赖性，如果一个指令Instruction 2必须用到Instruction 1的结果，那么处理器会保证Instruction 1会在Instruction 2之前执行。</p>
<p>　　虽然重排序不会影响单个线程内程序执行的结果，但是多线程呢？下面看一个例子：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//线程1:</span></span><br><span class="line">context = loadContext();   <span class="comment">//语句1</span></span><br><span class="line">inited = <span class="keyword">true</span>;             <span class="comment">//语句2</span></span><br><span class="line"> </span><br><span class="line"><span class="comment">//线程2:</span></span><br><span class="line"><span class="keyword">while</span>(!inited )&#123;</span><br><span class="line">  sleep()</span><br><span class="line">&#125;</span><br><span class="line">doSomethingwithconfig(context);</span><br></pre></td></tr></table></figure>

<p>上面代码中，由于语句1和语句2没有数据依赖性，因此可能会被重排序。假如发生了重排序，在线程1执行过程中先执行语句2，而此是线程2会以为初始化工作已经完成，那么就会跳 while循环，去执行<code>doSomethingwithconfig(context)</code>方法，而此时<code>context</code>并没有被初始化，就会导致程序出错。</p>
<p> 　   从上面可以看出，指令重排序不会影响单个线程的执行，但是会影响到线程并发执行的正确性。</p>
<p>　　也就是说，要想并发程序正确地执行，必须要保证原子性、可见性以及有序性。只要有一个没有被保证，就有可能会导致程序运行不正确。</p>
<h2 id="三-Java内存模型-JMM"><a href="#三-Java内存模型-JMM" class="headerlink" title="三.Java内存模型(JMM)"></a>三.Java内存模型(JMM)</h2><p>​        在前面谈到了一些关于内存模型以及并发编程中可能会出现的一些问题。下面我们来看一下Java内存模型，研究一下Java内存模型为我们提供了哪些保证以及在 java 中提供了哪些方法和机制来让我们在进行多线程编程时能够保证程序执行的正确性。</p>
<p>　　在Java虚拟机规范中试图定义一种Java内存模型（Java Memory Model，JMM）来屏蔽各个硬件平台和操作系统的内存访问差异，以实现让 Java 程序在各种平台下都能达到一致的内存访问效果。那么 Java 内存模型规定了哪些东西呢，它定义了程序中变量的访问规则，往大一点说是定义了程序执行的次序。注意，为了获得较好的执行性能，Java 内存模型并没有限制执行引擎使用处理器的寄存器或者高速缓存来提升指令执行速度，也没有限制编译器对指令进行重排序。也就是说，在 java 内存模型中，也会存在<strong>缓存一致性问题和指令重排序</strong>的问题。</p>
<p>　　Java 内存模型规定所有的变量都是存在主存当中（类似于前面说的物理内存），每个线程都有自己的工作内存（类似于前面的高速缓存）。线程对变量的所有操作都必须在工作内存中进行，而不能直接对主存进行操作。并且每个线程不能访问其他线程的工作内存。</p>
<p>举个简单的例子：在java中，执行下面这个语句：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">i = 10;</span><br></pre></td></tr></table></figure>

<p> 　执行线程必须先在自己的工作内存中对变量<code>i</code>所在的缓存行进行赋值操作，然后再写入主存当中。而不是直接将数值10写入主存当中。</p>
<p>　　那么 Java 语言本身对 <strong>原子性、可见性以及有序性</strong>提供了哪些保证呢？</p>
<h3 id="3-1-原子性"><a href="#3-1-原子性" class="headerlink" title="3.1 原子性"></a>3.1 原子性</h3><p>​        在Java中，对基本数据类型的变量的<strong>读取和赋值操作</strong>是原子性操作，即这些操作是不可被中断的，要么执行，要么不执行。</p>
<p>　　上面一句话虽然看起来简单，但是理解起来并不是那么容易。看下面一个例子：</p>
<p>　　请分析以下哪些操作是原子性操作：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">x = <span class="number">10</span>;         <span class="comment">//语句1</span></span><br><span class="line">y = x;         <span class="comment">//语句2</span></span><br><span class="line">x++;           <span class="comment">//语句3</span></span><br><span class="line">x = x + <span class="number">1</span>;     <span class="comment">//语句4</span></span><br></pre></td></tr></table></figure>

<p>事实上只有语句 1 是原子性操作，其他三个语句都不是原子性操作。</p>
<p>语句1是直接将数值10赋值给 x，也就是说线程执行这个语句的会直接将数值 10 写入到工作内存中。</p>
<p>语句2实际上包含 2 个操作，它先要去读取 x 的值，再将 x 的值写入工作内存，虽然读取 x 的值以及将 x 的值写入工作内存 这2个操作都是原子性操作，<strong>但是合起来就不是原子性操作了</strong>。</p>
<p>同样的，<code>x++</code>和 <code>x = x + 1</code>包括3个操作：<strong>读取 x 的值，进行加 1 操作，写入新的值</strong>。</p>
<p>所以上面 4 个语句只有语句 1 的操作具备原子性。</p>
<p>也就是说，只有简单的读取、赋值（而且必须是将数字赋值给某个变量，变量之间的相互赋值不是原子操作）才是原子操作。</p>
<h3 id="3-2-可见性"><a href="#3-2-可见性" class="headerlink" title="3.2 可见性"></a>3.2 可见性</h3><p>​        对于可见性，Java提供了<code>volatile</code>关键字来保证可见性。</p>
<p>　　当一个共享变量被<code>volatile</code>修饰时，它会保证修改的值会立即被更新到主存，当有其他线程需要读取时，它会去内存中读取新值。</p>
<p>　　而普通的共享变量不能保证可见性，因为普通共享变量被修改之后，什么时候被写入主存是不确定的，当其他线程去读取时，此时内存中可能还是原来的旧值，因此无法保证可见性。</p>
<p>　　另外，通过<code>synchronized</code>和<code>Lock</code>也能够保证可见性，<code>synchronized</code>和<code>Lock</code>能保证同一时刻只有一个线程获取锁然后执行同步代码，并且在释放锁之前会将对变量的修改刷新到主存当中。因此可以保证可见性。</p>
<h3 id="3-3-有序性"><a href="#3-3-有序性" class="headerlink" title="3.3 有序性"></a>3.3 有序性</h3><p>​        在Java内存模型中，允许编译器和处理器对指令进行重排序，但是重排序过程不会影响到单线程程序的执行，却会影响到多线程并发执行的正确性。</p>
<p>　　在Java里面，可以通过<code>volatile</code>关键字来保证一定的“有序性”（具体原理在下一节讲述）。另外可以通过<code>synchronized</code>和<code>Lock</code>来保证有序性，很显然，<code>synchronized</code>和<code>Lock</code>保证每个时刻是有一个线程执行同步代码，相当于是让线程顺序执行同步代码，自然就保证了有序性。</p>
<h2 id="四-深入剖析-volatile-关键字"><a href="#四-深入剖析-volatile-关键字" class="headerlink" title="四.深入剖析 volatile 关键字"></a>四.深入剖析 volatile 关键字</h2><p>在前面讲述了很多东西，其实都是为讲述<code>volatile</code>关键字作铺垫，那么接下来我们就进入主题。</p>
<h3 id="4-1-volatile-关键字的两层语义"><a href="#4-1-volatile-关键字的两层语义" class="headerlink" title="4.1 volatile 关键字的两层语义"></a>4.1 volatile 关键字的两层语义</h3><p>　一旦一个共享变量（类的成员变量、类的静态成员变量）被<code>volatile</code>修饰之后，那么就具备了两层语义：</p>
<p>　　1）保证了不同线程对这个变量进行操作时的<strong>可见性</strong>，即一个线程修改了某个变量的值，这新值对其他线程来说是立即可见的。</p>
<p>　　2）禁止进行指令重排序。</p>
<p>先看一段代码，假如线程1先执行，线程2后执行：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//线程1</span></span><br><span class="line"><span class="keyword">boolean</span> stop = <span class="keyword">false</span>;</span><br><span class="line"><span class="keyword">while</span>(!stop)&#123;</span><br><span class="line">    doSomething();</span><br><span class="line">&#125;</span><br><span class="line"> </span><br><span class="line"><span class="comment">//线程2</span></span><br><span class="line">stop = <span class="keyword">true</span>;</span><br></pre></td></tr></table></figure>

<p>​        这段代码是很典型的一段代码，很多人在中断线程时可能都会采用这种标记办法。但是事实上，这段代码会完全运行正确么？即一定会将线程中断么？不一定，也许在大多数时候，这个代码能够把线程中断，但是也有可能会导致无法中断线程（虽然这个可能性很小，但是只要一旦发生这种情况就会造成死循环了）。</p>
<p>下面解释一下这段代码为何有可能导致无法中断线程。在前面已经解释过，每个线程在运行过程中都有自己的工作内存，那么线程1在运行的时候，会将stop变量的值拷贝一份放在自己的工作内存当中。</p>
<p>　　那么当线程2更改了stop变量的值之后，但是还没来得及写入主存当中，线程2转去做其他事情了，那么线程1由于不知道线程2对stop变量的更改，因此还会一直循环下去。</p>
<p>　　但是用volatile修饰之后就变得不一样了：</p>
<p>　　第一：使用volatile关键字会强制将修改的值立即写入主存；</p>
<p>　　第二：使用volatile关键字的话，当线程2进行修改时，会导致线程1的工作内存中缓存变量stop的缓存行无效（反映到硬件层的话，就是CPU的L1或者L2缓存中对应的缓存行无效）；</p>
<p>　　第三：由于线程1的工作内存中缓存变量stop的缓存行无效，所以线程1再次读取变量stop的值时会去主存读取。</p>
<p>　　那么在线程2修改stop值时（当然这里包括2个操作，修改线程2工作内存中的值，然后将修改后的值写入内存），会使得线程1的工作内存中缓存变量stop的缓存行无效，然后线程1读取时，发现自己的缓存行无效，它会等待缓存行对应的主存地址被更新之后，然后去对应的主存读取最新的值。</p>
<p>　　那么线程1读取到的就是最新的正确的值。</p>
<h3 id="4-2-volatile保证原子性吗？"><a href="#4-2-volatile保证原子性吗？" class="headerlink" title="4.2 volatile保证原子性吗？"></a>4.2 volatile保证原子性吗？</h3><p>从上面知道<code>volatile</code>关键字保证了操作的可见性，但是<code>volatile</code>能保证对变量的操作是原子性吗？</p>
<p>　　下面看一个例子：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Test</span> </span>&#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">volatile</span> <span class="keyword">int</span> inc = <span class="number">0</span>;</span><br><span class="line">     </span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">increase</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        inc++;</span><br><span class="line">    &#125;</span><br><span class="line">     </span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">final</span> Test test = <span class="keyword">new</span> Test();</span><br><span class="line">        <span class="keyword">for</span>(<span class="keyword">int</span> i=<span class="number">0</span>;i&lt;<span class="number">10</span>;i++)&#123;</span><br><span class="line">            <span class="keyword">new</span> Thread()&#123;</span><br><span class="line">                <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>&#123;</span><br><span class="line">                    <span class="keyword">for</span>(<span class="keyword">int</span> j=<span class="number">0</span>;j&lt;<span class="number">1000</span>;j++)</span><br><span class="line">                        test.increase();</span><br><span class="line">                &#125;;</span><br><span class="line">            &#125;.start();</span><br><span class="line">        &#125;</span><br><span class="line">         </span><br><span class="line">        <span class="keyword">while</span>(Thread.activeCount()&gt;<span class="number">1</span>)  <span class="comment">//保证前面的线程都执行完</span></span><br><span class="line">            Thread.yield();</span><br><span class="line">        System.out.println(test.inc);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>大家想一下这段程序的输出结果是多少？也许有些朋友认为是 10000。但是事实上运行它会发现每次运行结果都不一致，都是一个小于 10000 的数字。</p>
<p>　　可能有的朋友就会有疑问，不对啊，上面是对变量 inc 进行自增操作，由于<code>volatile</code>保证了可见性，那么在每个线程中对 inc 自增完之后，在其他线程中都能看到修改后的值啊，所以有10个线程分别进行了1000次操作，那么最终inc 的值应该是 1000*10=10000。</p>
<p>　　这里面就有一个误区了，<code>volatile</code>关键字能保证可见性没有错，但是上面的程序错在没能保证原子性。可见性只能保证每次读取的是最新的值，但是<code>volatile</code>没办法保证对变量的操作的原子性。</p>
<p>　　在前面已经提到过，自增操作是不具备原子性的，它包括读取变量的原始值、进行加1操作、写入工作内存。那么就是说自增操作的三个子操作可能会分割开执行，就有可能导致下面这种情况出现：</p>
<ul>
<li>假如某个时刻变量 inc 的值为 10，</li>
<li>线程1对变量进行自增操作，线程1先读取了变量 inc 的原始值，然后线程1被阻塞了；</li>
<li>然后线程2对变量进行自增操作，线程2也去读取变量 inc 的原始值，由于线程1只是对变量 inc 进行读取操作, 而没有对变量进行修改操作，所以不会导致线程2的工作内存中缓存变量 inc 的缓存行无效，所以线程2会直接去主存读取 inc 的值，发现 inc 的值是10，然后进行加1操作，并把11写入工作内存，最后写入主存。</li>
<li>然后线程1接着进行加1操作，由于已经读取了inc的值，注意此时在线程1的工作内存中inc的值仍然为10，所以线程1对 inc 进行加1操作后 inc 的值为 11，然后将11写入工作内存，最后写入主存。</li>
</ul>
<p>　　那么两个线程分别进行了一次自增操作后，inc只增加了1。</p>
<p>　　解释到这里，可能有朋友会有疑问，不对啊，前面不是保证一个变量在修改<code>volatile</code>变量时，会让缓存行无效吗？然后其他线程去读就会读到新的值，对，这个没错。但是要注意，线程1对变量进行读取操作之后，被阻塞了的话，并没有对 inc 值进行修改。然后虽然<code>volatile</code>能保证线程2对变量 inc 的值读取是从内存中读取的，但是线程1没有进行修改，所以线程2根本就不会看到修改的值。</p>
<p>　　根源就在这里，自增操作不是原子性操作，而且<code>volatile</code>也无法保证对变量的任何操作都是原子性的。</p>
<p>把上面的代码改成以下写法可以达到效果：</p>
<p>采用synchronized：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Test</span> </span>&#123;</span><br><span class="line">    <span class="keyword">public</span>  <span class="keyword">int</span> inc = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title">increase</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        inc++;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">final</span> Test test = <span class="keyword">new</span> Test();</span><br><span class="line">        <span class="keyword">for</span>(<span class="keyword">int</span> i=<span class="number">0</span>;i&lt;<span class="number">10</span>;i++)&#123;</span><br><span class="line">            <span class="keyword">new</span> Thread()&#123;</span><br><span class="line">                <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>&#123;</span><br><span class="line">                    <span class="keyword">for</span>(<span class="keyword">int</span> j=<span class="number">0</span>;j&lt;<span class="number">1000</span>;j++)</span><br><span class="line">                        test.increase();</span><br><span class="line">                &#125;;</span><br><span class="line">            &#125;.start();</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">while</span>(Thread.activeCount()&gt;<span class="number">1</span>)  <span class="comment">//保证前面的线程都执行完</span></span><br><span class="line">            Thread.yield();</span><br><span class="line">        System.out.println(test.inc);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="4-3-volatile能保证有序性吗？"><a href="#4-3-volatile能保证有序性吗？" class="headerlink" title="4.3 volatile能保证有序性吗？"></a>4.3 volatile能保证有序性吗？</h3><p>在前面提到<code>volatile</code>关键字能禁止指令重排序，所以<code>volatile</code>能在一定程度上保证有序性。</p>
<p>　　<code>volatile</code>关键字禁止指令重排序有两层意思：</p>
<p>　　1）当程序执行到<code>volatile</code>变量的读操作或者写操作时，在其前面的操作的更改肯定全部已经进行，且结果已经对后面的操作可见；在其后面的操作肯定还没有进行；</p>
<p>　　2）在进行指令优化时，不能将在对<code>volatile</code>变量访问的语句放在其后面执行，也不能把<code>volatile</code>变量后面的语句放到其前面执行。</p>
<p>　　可能上面说的比较绕，举个简单的例子：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//x、y为非volatile变量</span></span><br><span class="line"><span class="comment">//flag为volatile变量</span></span><br><span class="line"> </span><br><span class="line">x = <span class="number">2</span>;        <span class="comment">//语句1</span></span><br><span class="line">y = <span class="number">0</span>;        <span class="comment">//语句2</span></span><br><span class="line">flag = <span class="keyword">true</span>;  <span class="comment">//语句3</span></span><br><span class="line">x = <span class="number">4</span>;         <span class="comment">//语句4</span></span><br><span class="line">y = -<span class="number">1</span>;       <span class="comment">//语句5</span></span><br></pre></td></tr></table></figure>

<p>​        由于 flag 变量为<code>volatile</code>变量，那么在进行指令重排序的过程的时候，不会将语句3放到语句1、语句2前面，也不会讲语句3放到语句4、语句5后面。但是要注意语句1和语句2的顺序、语句4和语句5的顺序是不作任何保证的。</p>
<p>　　 并且<code>volatile</code>关键字能保证，执行到语句3时，语句1和语句2必定是执行完毕了的，且语句1和语句2的执行结果对语句3、语句4、语句5是可见的。</p>
<p>　　那么我们回到前面举的一个例子：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//线程1:</span></span><br><span class="line">context = loadContext();   <span class="comment">//语句1</span></span><br><span class="line">inited = <span class="keyword">true</span>;             <span class="comment">//语句2</span></span><br><span class="line"> </span><br><span class="line"><span class="comment">//线程2:</span></span><br><span class="line"><span class="keyword">while</span>(!inited )&#123;</span><br><span class="line">  sleep()</span><br><span class="line">&#125;</span><br><span class="line">doSomethingwithconfig(context);</span><br></pre></td></tr></table></figure>

<p>​        前面举这个例子的时候，提到有可能语句2会在语句1之前执行，那么久可能导致<code>context</code>还没被初始化，而线程2中就使用未初始化的<code>context</code>去进行操作，导致程序出错。</p>
<p>　　这里如果用<code>volatile</code>关键字对 inited 变量进行修饰，就不会出现这种问题了，因为当执行到语句2时，必定能保证<code>context</code> 已经初始化完毕。</p>
<h2 id="五-使用-volatile-关键字的场景"><a href="#五-使用-volatile-关键字的场景" class="headerlink" title="五.使用 volatile 关键字的场景"></a>五.使用 volatile 关键字的场景</h2><p><code>synchronized</code>关键字是防止多个线程同时执行一段代码，那么就会很影响程序执行效率，而<code>volatile</code>关键字在某些情况下性能要优于<code>synchronized</code>，但是要注意<code>volatile</code>关键字是无法替代<code>synchronized</code>关键字的，因为<code>volatile</code>关键字无法保证操作的原子性。通常来说，使用<code>volatile</code>必须具备以下2个条件：</p>
<p>　　 （1）对变量的写操作不依赖于当前值（比如 <code>i++</code>），或者说是单纯的变量赋值（<code>boolean  flag = true</code>）。 </p>
<p>​        （2）该变量没有包含在具有其他变量的不变式中，也就是说，不同的 volatile 变量之间，不能互相依赖。只有在状态真正独立于程序内其他内容时才能使用 <code>volatile</code>。</p>
<p>　　实际上，这些条件表明，可以被写入 <code>volatile</code> 变量的这些有效值独立于任何程序的状态，包括变量的当前状态。</p>
<p>　　事实上，我的理解就是上面的 2 个条件需要保证操作是原子性操作，才能保证使用<code>volatile</code>关键字的程序在并发时能够正确执行。</p>
<h2 id="六：synchronized-和-volatile-的区别"><a href="#六：synchronized-和-volatile-的区别" class="headerlink" title="六：synchronized 和 volatile 的区别"></a>六：synchronized 和 volatile 的区别</h2><p>作为 Java 中最常见的两种锁，synchronized 和 volatile 有什么区别呢？</p>
<p>首先，必须明确的是这两个关键字是互补的存在，而不是对立的存在，其次，他们的区别主要体现在：</p>
<ul>
<li>volatile 关键字是线程同步的轻量级实现，所以 volatile 性能肯定比 synchronized 关键字要好。但是 volatile 关键字只能用于变量而 synchronized 关键字可以修饰方法以及代码块。</li>
<li>volatile 关键字能保证数据的可见性，但不能保证数据的原子性。synchronized 关键字两者都能保证。</li>
<li>volatile 关键字主要用于<strong>解决变量在多个线程之间的可见性</strong>，而 synchronized 关键字解决的是<strong>多个线程之间访问资源的同步性</strong>。</li>
</ul>
<p>说到底，在计算机的世界里凡事讲究的是<strong>均衡</strong>，没有谁好谁好谁坏之说，具体用什么还是要视具体的需求而定。</p>
<h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><p>《深入理解Java虚拟机》</p>
<p>　　<a target="_blank" rel="noopener" href="http://jiangzhengjun.iteye.com/blog/652532">http://jiangzhengjun.iteye.com/blog/652532</a></p>
<p>　　<a target="_blank" rel="noopener" href="http://blog.sina.com.cn/s/blog_7bee8dd50101fu8n.html">http://blog.sina.com.cn/s/blog_7bee8dd50101fu8n.html</a></p>
<p>　　<a target="_blank" rel="noopener" href="http://ifeve.com/volatile/">http://ifeve.com/volatile/</a></p>
<p>　　<a target="_blank" rel="noopener" href="http://blog.csdn.net/ccit0519/article/details/11241403">http://blog.csdn.net/ccit0519/article/details/11241403</a></p>
<p>　　<a target="_blank" rel="noopener" href="http://blog.csdn.net/ns_code/article/details/17101369">http://blog.csdn.net/ns_code/article/details/17101369</a></p>
<p>　　<a target="_blank" rel="noopener" href="http://www.cnblogs.com/kevinwu/archive/2012/05/02/2479464.html">http://www.cnblogs.com/kevinwu/archive/2012/05/02/2479464.html</a></p>
<p>　　<a target="_blank" rel="noopener" href="http://www.cppblog.com/elva/archive/2011/01/21/139019.html">http://www.cppblog.com/elva/archive/2011/01/21/139019.html</a></p>
<p>　　<a target="_blank" rel="noopener" href="http://ifeve.com/volatile-array-visiblity/">http://ifeve.com/volatile-array-visiblity/</a></p>
<p>　　<a target="_blank" rel="noopener" href="http://www.bdqn.cn/news/201312/12579.shtml">http://www.bdqn.cn/news/201312/12579.shtml</a></p>
<p>​       <a target="_blank" rel="noopener" href="https://www.cnblogs.com/dolphin0520/p/3920373.html">https://www.cnblogs.com/dolphin0520/p/3920373.html</a></p>

    </div>

    
    
    

    <footer class="post-footer">
          <div class="post-tags">
              <a href="/tags/java-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/" rel="tag"># java 并发编程</a>
          </div>

        

          <div class="post-nav">
            <div class="post-nav-item">
                <a href="/2022/02/15/%E9%87%8D%E8%AF%BB%20HashMap%20%E6%BA%90%E7%A0%81%EF%BC%8C%E5%80%BC%E5%BE%97%E4%B8%80%E5%93%81/" rel="prev" title="重读 HashMap 源码，值得一品">
                  <i class="fa fa-chevron-left"></i> 重读 HashMap 源码，值得一品
                </a>
            </div>
            <div class="post-nav-item">
                <a href="/2022/03/01/%E6%B5%85%E8%B0%88Netty%E6%A0%B8%E5%BF%83%E7%BB%84%E4%BB%B6/" rel="next" title="浅谈 Netty 核心组件">
                  浅谈 Netty 核心组件 <i class="fa fa-chevron-right"></i>
                </a>
            </div>
          </div>
    </footer>
  </article>
</div>






</div>
  </main>

  <footer class="footer">
    <div class="footer-inner">


<div class="copyright">
  &copy; 
  <span itemprop="copyrightYear">2022</span>
  <span class="with-love">
    <i class="fa fa-heart"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">AiLaodu</span>
</div>
  <div class="powered-by">由 <a href="https://hexo.io/" rel="noopener" target="_blank">Hexo</a> & <a href="https://theme-next.js.org/muse/" rel="noopener" target="_blank">NexT.Muse</a> 强力驱动
  </div>

    </div>
  </footer>

  
  <script src="https://cdn.jsdelivr.net/npm/animejs@3.2.1/lib/anime.min.js" integrity="sha256-XL2inqUJaslATFnHdJOi9GfQ60on8Wx1C2H8DYiN1xY=" crossorigin="anonymous"></script>
<script src="/js/comments.js"></script><script src="/js/utils.js"></script><script src="/js/motion.js"></script><script src="/js/schemes/muse.js"></script><script src="/js/next-boot.js"></script>

  
<script src="https://cdn.jsdelivr.net/npm/algoliasearch@4.10.5/dist/algoliasearch-lite.umd.js" integrity="sha256-HWlivbjXc58GuU4EIZziqIE83FFZ/da42de13pGZnMA=" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/instantsearch.js@4.30.2/dist/instantsearch.production.min.js" integrity="sha256-eqhx/eer5fsD7YooSb21wsfJaQkJ/4gF3bmRBZP7q2o=" crossorigin="anonymous"></script><script src="/js/third-party/search/algolia-search.js"></script>





  





</body>
</html>
